/**
 * Copyright 2019 吉鼎科技.

 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.easyplatform.engine.cmd.api;

import cn.easyplatform.contexts.RecordContext;
import cn.easyplatform.contexts.WorkflowContext;
import cn.easyplatform.dao.IdentityDao;
import cn.easyplatform.dao.Page;
import cn.easyplatform.dos.FieldDo;
import cn.easyplatform.dos.LogDo;
import cn.easyplatform.dos.Record;
import cn.easyplatform.dos.UserDo;
import cn.easyplatform.engine.cmd.identity.ApiInitCmd;
import cn.easyplatform.engine.cmd.task.NextCmd;
import cn.easyplatform.engine.runtime.ComponentProcessFactory;
import cn.easyplatform.engine.runtime.DataListProcess;
import cn.easyplatform.engine.runtime.RuntimeTask;
import cn.easyplatform.engine.runtime.RuntimeTaskFactory;
import cn.easyplatform.entities.BaseEntity;
import cn.easyplatform.entities.beans.LogicBean;
import cn.easyplatform.entities.beans.api.ApiBean;
import cn.easyplatform.entities.beans.api.Input;
import cn.easyplatform.entities.beans.api.Output;
import cn.easyplatform.entities.beans.bpm.BpmBean;
import cn.easyplatform.entities.beans.list.ListBean;
import cn.easyplatform.entities.beans.report.JasperReportBean;
import cn.easyplatform.entities.beans.task.TaskBean;
import cn.easyplatform.i18n.I18N;
import cn.easyplatform.interceptor.AbstractCommand;
import cn.easyplatform.interceptor.CommandContext;
import cn.easyplatform.lang.Strings;
import cn.easyplatform.lang.Times;
import cn.easyplatform.log.LogManager;
import cn.easyplatform.messages.request.ApiInitRequestMessage;
import cn.easyplatform.messages.request.ApiRequestMessage;
import cn.easyplatform.messages.request.NextRequestMessage;
import cn.easyplatform.messages.response.BatchResponseMessage;
import cn.easyplatform.messages.response.PageResponseMessage;
import cn.easyplatform.messages.response.SimpleResponseMessage;
import cn.easyplatform.messages.vos.*;
import cn.easyplatform.support.sql.SqlParser;
import cn.easyplatform.type.*;
import cn.easyplatform.util.EntityUtils;
import cn.easyplatform.util.MessageUtils;
import cn.easyplatform.util.RuntimeUtils;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.export.HtmlExporter;
import net.sf.jasperreports.export.SimpleExporterInput;
import net.sf.jasperreports.export.SimpleHtmlExporterConfiguration;
import net.sf.jasperreports.export.SimpleHtmlExporterOutput;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayOutputStream;
import java.util.*;

import static cn.easyplatform.type.UserType.TYPE_ADMIN;
import static cn.easyplatform.type.UserType.TYPE_API;

/**
 * @Author: <a href="mailto:davidchen@epclouds.com">littleDog</a> <br/>
 * @Description:
 * @Since: 2.0.0 <br/>
 * @Date: Created in 2019/4/2 10:16
 * @Modified By:
 */
public class ApiCmd extends AbstractCommand<ApiRequestMessage> {

    private final static Logger log = LoggerFactory.getLogger(ApiCmd.class);

    /**
     * @param req
     */
    public ApiCmd(ApiRequestMessage req) {
        super(req);
    }

    @Override
    public IResponseMessage<?> execute(CommandContext cc) {
        boolean close = false;
        try {
            if (req.getBody() instanceof ApiExecuteVo) {//直接执行需要先验证
                ApiExecuteVo ae = (ApiExecuteVo) req.getBody();
                ApiInitVo ai = new ApiInitVo();
                ai.setNodeIp(ae.getNodeIp());
                ai.setOrgId(ae.getOrgId());
                ai.setServiceId(ae.getServiceId());
                ai.setUserId(ae.getNodeId());
                ai.setUserPass(ae.getNodePass());
                ai.setDeviceType(DeviceType.API.getName());
                ApiInitCmd cmd = new ApiInitCmd(new ApiInitRequestMessage(ai));
                IResponseMessage<?> resp = cmd.execute(cc);
                if (!resp.isSuccess())
                    return resp;
                close = true;
            }
            if (cc.getProjectService() == null || cc.getUser() == null) {
                cc.logout();
                return new SimpleResponseMessage("E000", I18N.getLabel("api.token.expired"));
            }
            ApiVo av = req.getBody();
            ApiBean ab = cc.getEntity(av.getId());
            if (ab == null || Strings.isBlank(ab.getRefId()))
                return MessageUtils.entityNotFound(EntityType.API.getName(),
                        av.getId());
            //防止不正常的调用
            if (cc.getUser().getType() >= UserType.TYPE_OAUTH && !ab.isAnonymous())
                return new SimpleResponseMessage("e200", I18N.getLabel("api.access.error"));

            //检查传入的参数
            if (av.getData() != null && ab.getInputs() != null) {
                for (Input input : ab.getInputs()) {
                    if (input.isRequired() && !av.getData().containsKey(input.getName()))
                        return new SimpleResponseMessage("a001", I18N.getLabel("api.input.required", input.getName(), av.getId()));
                }
            }
            if (ab.getRefId().toLowerCase().startsWith("select ")) {
                if (!Strings.isBlank(av.getUser()))
                    createUser(cc, av);
                //设置默认的角色
                if (Strings.isBlank(av.getRole()) && cc.getUser().getRoles() != null && cc.getUser().getRoles().length > 0)
                    av.setRole(cc.getUser().getRoles()[0]);
                int pageNo = -1, pageSize = 0;
                if (av.getStartNo() != null && av.getStartNo() >= 0)
                    pageNo = av.getStartNo();
                if (av.getPageSize() != null && av.getPageSize() > 0)
                    pageSize = av.getPageSize();
                RecordContext rc = createContext(av, ab);
                if (pageNo > 0) {//多笔数据
                    Page page = null;
                    if (pageSize > 0) {
                        page = new Page(pageNo, pageSize);
                        page.setGetTotal(av.getGetTotal() == null || av.getGetTotal());
                    }
                    SqlParser<Object> parser = RuntimeUtils.createSqlParser(Object.class);
                    List<Map<String, Object>> data = cc.getBizDao().selectMapList(parser.parse(ab.getRefId(), rc), page, parser.getParams().toArray());
                    if (page != null && page.isGetTotal()) {
                        Map<String, Object> map = new HashMap<>();
                        map.put("totalSize", page.getTotalCount());
                        map.put("data", data);
                        return new SimpleResponseMessage(map);
                    }
                    return new SimpleResponseMessage(data);
                } else {//单笔数据
                    SqlParser<Object> parser = RuntimeUtils.createSqlParser(Object.class);
                    return new SimpleResponseMessage(cc.getBizDao().selectMap(parser.parse(ab.getRefId(), rc), parser.getParams().toArray()));
                }
            } else {
                BaseEntity entity = cc.getEntity(ab.getRefId());
                if (entity == null)
                    return MessageUtils.entityNotFound(EntityType.API.getName(),
                            ab.getRefId());
                if (!Strings.isBlank(av.getUser()))
                    createUser(cc, av);
                //设置默认的角色
                if (Strings.isBlank(av.getRole()) && cc.getUser().getRoles() != null && cc.getUser().getRoles().length > 0) {
                    av.setRole(cc.getUser().getRoles()[0]);
                }
                if (entity instanceof TaskBean) {//执行task
                    TaskBean task = (TaskBean) entity;
                    BaseEntity base = cc.getEntity(task.getRefId());
                    if (base == null)
                        return MessageUtils.entityNotFound(EntityType.PAGE.getName(),
                                task.getRefId());
                    BpmBean bb = null;
                    if (base instanceof BpmBean) {// 工作流
                        bb = (BpmBean) base;
                        String taskId = cc.getBpmEngine().getInstanceTask(bb.getId());
                        if (taskId == null)
                            return MessageUtils.entityNotFound(
                                    EntityType.TASK.getName(), taskId);
                        task = cc.getEntity(taskId);
                        if (task == null)
                            return MessageUtils.entityNotFound(
                                    EntityType.TASK.getName(), taskId);
                        task = EntityUtils.getInheritTask(cc, null, task);
                        base = cc.getEntity(task.getRefId());
                        if (base == null)
                            return MessageUtils.entityNotFound(
                                    EntityType.PAGE.getName(), task.getRefId());
                    } else
                        task = EntityUtils.getInheritTask(cc, null, task);
                    task = task.clone();
                    task.setName(RuntimeUtils.getLabel(cc, task.getName()));
                    if (!Strings.isBlank(av.getProcessCode()))
                        task.setProcessCode(av.getProcessCode());
                    RuntimeTask runtime = RuntimeTaskFactory.createRuntime(base.getType());
                    WorkflowContext ctx = createContext(task, av, ab);
                    cc.setWorkflowContext(ctx);
                    if (bb != null)
                        RuntimeUtils.initBpm(ctx, bb);
                    if (cc.getUser().getLogLevel() >= LogDo.LEVEL_TASK)
                        RuntimeUtils.log(cc, LogDo.TYPE_TASK, "begin", task.getId());
                    IResponseMessage<?> resp = runtime.doTask(cc, base);
                    if (!resp.isSuccess())
                        return resp;
                    while (!(resp instanceof SimpleResponseMessage)) {
                        AbstractPageVo pv = null;
                        NextVo nv = new NextVo(null);
                        if (resp instanceof PageResponseMessage) {
                            pv = ((PageResponseMessage) resp).getBody();
                            nv.setData(new HashMap<String, Object>());
                        } else if (resp instanceof BatchResponseMessage) {
                            pv = ((BatchResponseMessage) resp).getBody();
                        }
                        NextRequestMessage nreq = new NextRequestMessage(pv.getId(),
                                nv);
                        NextCmd nextCmd = new NextCmd(nreq);
                        resp = nextCmd.execute(cc);
                        if (!resp.isSuccess())
                            return resp;
                    }
                    return createResponse(ctx, ab);
                } else if (entity instanceof LogicBean) {//执行逻辑
                    LogicBean logic = (LogicBean) entity;
                    TaskBean task = new TaskBean();
                    task.setId(ab.getId());
                    task.setName(ab.getName());
                    task.setDescription(ab.getDescription());
                    WorkflowContext ctx = createContext(task, av, ab);
                    cc.setWorkflowContext(ctx);
                    if (cc.getUser().getLogLevel() >= LogDo.LEVEL_TASK)
                        RuntimeUtils.log(cc, LogDo.TYPE_TASK, "begin", ab.getId());
                    if (av.getStartNo() != null && av.getStartNo() >= 0) {
                        ctx.getRecord().setVariable(new FieldDo("PAGE_NO", FieldType.INT, av.getStartNo()));
                        if (av.getPageSize() != null && av.getPageSize() > 0)
                            ctx.getRecord().setVariable(new FieldDo("PAGE_SIZE", FieldType.INT, av.getStartNo()));
                        if (av.getGetTotal() != null)
                            ctx.getRecord().setVariable(new FieldDo("GET_TOTAL", FieldType.BOOLEAN, av.getGetTotal()));
                    }
                    try {
                        cc.beginTx();
                        Object result = RuntimeUtils.eval(cc, logic, ctx.getRecord());
                        if (!"0000".equals(result)) {//不成功
                            cc.rollbackTx();
                            return MessageUtils.byErrorCode(cc, ctx.getRecord(), ctx.getId(), (String) result);
                        }
                        cc.commitTx();
                        return createResponse(ctx, ab);
                    } catch (Exception e) {
                        cc.rollbackTx();
                        return MessageUtils.getMessage(e, log);
                    } finally {
                        cc.closeTx();
                    }
                } else if (entity instanceof ListBean) {//执行列表
                    TaskBean task = new TaskBean();
                    task.setId(ab.getId());
                    task.setName(ab.getName());
                    task.setDescription(ab.getDescription());
                    WorkflowContext ctx = createContext(task, av, ab);
                    cc.setWorkflowContext(ctx);
                    if (cc.getUser().getLogLevel() >= LogDo.LEVEL_TASK)
                        RuntimeUtils.log(cc, LogDo.TYPE_TASK, "begin", ab.getId());
                    ListBean list = (ListBean) entity;
                    if (list.getOnInit() != null) {
                        String code = RuntimeUtils.eval(cc, list.getOnInit(), ctx.getRecord());
                        if (!code.equals("0000"))
                            return MessageUtils.byErrorCode(cc, ctx.getRecord(), ctx.getId(), code);
                    }
                    ListInitVo iv = new ListInitVo();
                    iv.setId(ctx.getParameterAsString("800"));
                    iv.setImmediate(true);
                    iv.setType(Constants.CATALOG);

                    if (av.getStartNo() != null && av.getStartNo() >= 0)
                        iv.setStartPageNo(av.getStartNo());
                    else
                        iv.setStartPageNo(0);
                    if (av.getPageSize() != null && av.getPageSize() > 0)
                        iv.setPageSize(av.getPageSize());
                    //else
                    //    iv.setPageSize(20);

                    DataListProcess dlp = ComponentProcessFactory
                            .createDatalistProcess(Constants.CATALOG);
                    cn.easyplatform.messages.vos.datalist.ListVo lv = dlp.init(cc, list, iv);
                    List<Map<String, Object>> data = new ArrayList<>();
                    int size = lv.getHeaders().size();
                    for (ListRowVo rv : lv.getData()) {
                        Map<String, Object> row = new HashMap<>();
                        for (int i = 0; i < size; i++) {
                            if (ab.getOutputs() != null && !ab.getOutputs().isEmpty()) {//是否有限制列
                                for (Output output : ab.getOutputs()) {
                                    if (output.getName().equalsIgnoreCase(lv.getHeaders().get(i).getField())) {
                                        row.put(output.getName(), rv.getData()[i]);
                                        break;
                                    }
                                }
                            } else
                                row.put(lv.getHeaders().get(i).getField(), rv.getData()[i]);
                        }
                        data.add(row);
                    }
                    Map<String, Object> map = new HashMap<>();
                    map.put("totalSize", lv.getTotalSize());
                    map.put("data", data);
                    return new SimpleResponseMessage(map);
                } else if (entity instanceof JasperReportBean) {//报表
                    TaskBean task = new TaskBean();
                    task.setId(ab.getId());
                    task.setName(ab.getName());
                    task.setDescription(ab.getDescription());
                    WorkflowContext ctx = createContext(task, av, ab);
                    cc.setWorkflowContext(ctx);
                    if (cc.getUser().getLogLevel() >= LogDo.LEVEL_TASK)
                        RuntimeUtils.log(cc, LogDo.TYPE_TASK, "begin", ab.getId());
                    JasperReportBean rb = (JasperReportBean) entity;
                    ctx.setParameter("830", EntityType.REPORT.getName());
                    ctx.setParameter("831", rb.getId()).setParameter("832",
                            rb.getName());
                    ctx.setParameter("833", "");
                    ReportVo rv = new ReportVo(ctx.getParameterAsString("800"), task.getRefId(), null, null, 'R', null, null, null, false);
                    //输出html
                    JasperPrint jp = (JasperPrint) ComponentProcessFactory.createComponentProcess(entity).doComponent(cc, rv, rb);
                    HtmlExporter exporter = new HtmlExporter();
                    exporter.setExporterInput(new SimpleExporterInput(jp));
                    SimpleHtmlExporterConfiguration configuration = new SimpleHtmlExporterConfiguration();
                    configuration.setFlushOutput(true);
                    exporter.setConfiguration(configuration);
                    ByteArrayOutputStream bos = new ByteArrayOutputStream();
                    SimpleHtmlExporterOutput exporterOutput = new SimpleHtmlExporterOutput(
                            bos, "utf-8");
                    try {
                        exporter.setExporterOutput(exporterOutput);
                        exporter.exportReport();
                        return new SimpleResponseMessage(bos.toString());
                    } finally {
                        exporterOutput.close();
                        exporterOutput = null;
                        exporter = null;
                    }
                } else
                    return new SimpleResponseMessage("a002", I18N.getLabel("api.not.support.type", av.getId()));
            }
        } catch (
                Exception e) {
            return MessageUtils.getMessage(e, log);
        } finally {
            cc.removeWorkflowContext();
            if (close) {
                cc.logout();
                LogManager.stopUser(cc.getUser());
            }
        }

    }

    private IResponseMessage<?> createUser(CommandContext cc, ApiVo av) {
        IdentityDao dao = cc.getIdentityDao(true);
        UserDo user = dao.getUser(cc.getProjectService().getConfig()
                .getAuthenticationQuery(), av.getUser());
        if (user == null)
            return MessageUtils.userNotFound(av.getUser());
        dao.setUser(user);
        if (user.getType() == TYPE_API || user.getType() == TYPE_ADMIN)//API或admin用户不能登陆
            return MessageUtils.userNotFound(av.getUser());
        if (user.getValidDate() != null) {
            Date now = Times.toDay();
            if (now.before(user.getValidDate()))  // 设置的有效开始日期还未到
                return MessageUtils.userPasswordExpired(av.getUser());
            if (user.getValidDays() > 0) {// 是否过期
                int oneDay = 24 * 60 * 60 * 1000;
                Date when = new Date(user.getValidDate().getTime()
                        + user.getValidDays() * oneDay);
                if (now.after(when))
                    return MessageUtils.userPasswordExpired(av.getUser());
            }
        }
        user.setId(av.getUser());
        user.setIp(cc.getUser().getIp());
        user.setOrg(cc.getUser().getOrg());
        user.setRoles(cc.getUser().getRoles());
        user.setDeviceType(DeviceType.API);
        user.setLocale(cc.getUser().getLocale());
        user.setLoginDate(new Date());
        cc.setUser(user);
        return null;
    }

    /**
     * @Author <a href="mailto:davidchen@epclouds.com">littleDog</a> <br/>
     * @Description 创建简单的功能环境变量
     * @Since 2.0.0 <br/>
     * @Date 16:38 2019/4/2
     * @Param [tb, av, ab]
     * @Return cn.easyplatform.contexts.WorkflowContext
     **/
    private WorkflowContext createContext(TaskBean tb, ApiVo av, ApiBean ab) {
        TaskVo tv = new TaskVo(tb.getId());
        tv.setRoleId(av.getRole());
        WorkflowContext ctx = new WorkflowContext(tb, tv);
        if (av.getData() != null && ab.getInputs() != null) {
            for (Input input : ab.getInputs()) {
                Object value = av.getData().get(input.getName());
                FieldDo var = new FieldDo(input.getType(), value);
                var.setName(input.getName());
                var.setLength(input.getLength());
                var.setDecimal(input.getDecimal());
                var.setScope(input.getScope());
                var.setDescription(input.getDesp());
                var.setAcc(input.getAcc());
                var.setShareModel(input.getShareModel());
                ctx.getRecord().setVariable(var);
            }
        }
        return ctx;
    }

    /**
     * @param av
     * @param ab
     * @return
     */
    private RecordContext createContext(ApiVo av, ApiBean ab) {
        Map<String, Object> systemVariables = new HashMap<String, Object>();
        RuntimeUtils.initWorkflow(systemVariables, null);
        RecordContext rc = new RecordContext(new Record(), systemVariables, new HashMap<>());
        if (av.getData() != null && ab.getInputs() != null) {
            for (Input input : ab.getInputs()) {
                Object value = av.getData().get(input.getName());
                FieldDo var = new FieldDo(input.getType(), value);
                var.setName(input.getName());
                var.setLength(input.getLength());
                var.setDecimal(input.getDecimal());
                var.setScope(input.getScope());
                var.setDescription(input.getDesp());
                var.setAcc(input.getAcc());
                var.setShareModel(input.getShareModel());
                rc.setVariable(var);
            }
        }
        return rc;
    }

    /**
     * @Author <a href="mailto:davidchen@epclouds.com">littleDog</a> <br/>
     * @Description 创建返回值
     * @Since 2.0.0 <br/>
     * @Date 17:44 2019/4/2
     * @Param [ctx, ab]
     * @Return cn.easyplatform.messages.response.SimpleResponseMessage
     **/
    private SimpleResponseMessage createResponse(WorkflowContext ctx, ApiBean ab) {
        Map<String, Object> data = new HashMap<>();
        if (ab.getOutputs() != null && !ab.getOutputs().isEmpty()) {
            for (Output output : ab.getOutputs()) {
                FieldDo fieldDo = ctx.getRecord().getFieldQuietly(output.getName());
                if (fieldDo != null)
                    data.put(output.getName(), fieldDo.getValue());
                else if (output.isRequired())
                    return new SimpleResponseMessage("a003", I18N.getLabel("api.output.required", output.getName(), ab.getId()));
            }
        } else {
            for (FieldDo field : ctx.getRecord().getRecordFieldValues())
                data.put(field.getRawName(), field.getValue());
            for (FieldDo field : ctx.getRecord().getUserVars()) {
                if (Strings.isBlank(field.getName()))
                    data.put(field.getRawName(), field.getValue());
            }
        }
        return new SimpleResponseMessage(data);
    }
}
